/*
 * Copyright:  2018 smarabbit studio.
 *
 *  Licensed under the Confluent Community License; you may not use this file
 *  except in compliance with the License.  You may obtain a copy of the License at
 *
 *  http://www.confluent.io/confluent-community-license
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 *  WARRANTIES OF ANY KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations under the License.
 *
 *  @作   者： 黄开晖 (117227773@qq.com)
 *  @日   期:  2020年9月16日
 */
package com.massyframework.beanskin.web.servlet;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicInteger;

import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.massyframework.beanskin.Module;
import com.massyframework.beanskin.service.ServiceNotFoundException;
import com.massyframework.beanskin.service.ServiceReference;
import com.massyframework.beanskin.service.ServiceRepository;
import com.massyframework.beanskin.service.ServiceTracker;
import com.massyframework.beanskin.service.ServiceTrackerCustomizer;
import com.massyframework.beanskin.util.classloading.ClassLoaderUtils;
import com.massyframework.beanskin.util.sorting.OrderComparator;
import com.massyframework.beanskin.web.HttpProcessor;
import com.massyframework.beanskin.web.ModularHttpServletRequest;
import com.massyframework.beanskin.web.ModularServletContext;

/**
 * 为装配件内部的Servlet提供代理。<br>
 * 在运行框架启动阶段，先向ServletContext注册DelegateServlet，当装配件进入正常工作态后，
 * 才将Http请求转给装配件内的Servlet处理。
 * 
 * @author huangkh
 *
 */
public class DelegateServlet extends ModuleServlet
	implements ServiceTrackerCustomizer<Servlet>{
    
    /**
     * 支持的能力
     */
    public static final String SUPPORTS           = "supports";

    /**
     * 是否需要支持jsp.taglib标签
     */
    public static final String TAGLIB_SUPPORT     = "jsp.taglib.support";
    
    /**
     * 忽略初始化错误。
     * <br>
     * 对于支持脚本类，可重复调整的Servlet，对于初始化期间的例外需要忽略。
     */
    public static final String IGNORE_INIT_ERROR  = "ignore.init.error";
    
    
    private static final Logger LOG = LoggerFactory.getLogger(DelegateServlet.class);

    private ServiceTracker<Servlet> tracker;
    private volatile ClassLoader classLoader;
    private volatile Servlet internelServlet;
    private volatile ServletException initException;
    private AtomicInteger invokeTimes;
    

    private List<HttpProcessor> processors;

    public DelegateServlet() {
        this.invokeTimes = new AtomicInteger();
    }

    /* (non-Javadoc)
     * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
     */
    @Override
    public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
        Servlet servlet = this.internelServlet;
        if (servlet != null) {
            if (this.initException != null) {
                throw this.initException;
            }

            ClassLoader contextLoader =
                    ClassLoaderUtils.setThreadContextClassLoader(this.classLoader);
            try {
                this.invokeTimes.incrementAndGet();
                ModularHttpServletRequest request = new ModularHttpServletRequest(
                        (HttpServletRequest)req,
                        (ModularServletContext)this.getServletConfig().getServletContext());

                boolean completed = false;
                for (HttpProcessor processor: this.processors){
                	completed = processor.process(request, (HttpServletResponse)res);
                    if (completed){
                        break;
                    }
                }

                if (!completed) {
                    servlet.service(request, res);
                }
            }finally {
                this.invokeTimes.decrementAndGet();
                ClassLoaderUtils.setThreadContextClassLoader(contextLoader);
            }
        }else {
            if (this.initException != null){
            	throw new ServletException(this.initException.getMessage(), this.initException);
            }else {
            	StringBuilder builder = new StringBuilder();
            	builder.append("cannot found servie with filter:")
            		.append(this.genericFilterString());
                throw new ServiceNotFoundException(builder.toString());
            }
        }
    }

    /* (non-Javadoc)
     * @see com.massyframework.assembly.web.servlet.AbstractServlet#init()
     */
    @Override
    protected synchronized void init() throws ServletException {
        super.init();
        if (this.tracker == null){
            Module<?> module = this.getAssociatedModule();
            this.classLoader = module.getModuleClassLoader();
           
            String filterString = this.genericFilterString();
            this.tracker = new ServiceTracker<Servlet>(
                    module,
                    Servlet.class,
                    filterString,
                    this);
            this.tracker.open();

            String text = this.getServletConfig().getInitParameter(SUPPORTS);
            if (text != null) {
            	List<HttpProcessor> processors = new ArrayList<>();
            	String[] arr = text.split(",");
            	ServiceRepository repo = ServiceRepository.retrieveFrom(module);
            	for (String name: arr) {
            		name = name.trim();
            		if (name.length() > 0) {
            			Optional<HttpProcessor> optional = 
            					repo.findService(HttpProcessor.class, "(service.name=".concat(name).concat(")"));
            			optional.ifPresent( processor -> processors.add(processor));            			
            		}
            	}
            	
            	Collections.sort(processors, new OrderComparator<HttpProcessor>());
            	this.processors = Collections.unmodifiableList(processors);
            }else {
            	this.processors = Collections.emptyList();
            }
        }

        LOG.debug("init Servlet[" + this.getServletConfig().getServletName() + "].");
    }

    /* (non-Javadoc)
     * @see com.massyframework.assembly.web.servlet.AbstractServlet#destroy()
     */
    @Override
    public synchronized void destroy() {
        if (this.tracker != null){
            for (HttpProcessor processor: this.processors){
                processor.destroy();
            }
            this.tracker.close();
            this.tracker = null;

            if (this.internelServlet != null) {
                //等待调用全部完成
                while (this.invokeTimes.get() != 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                    }
                }

                ClassLoader contextLoader =
                        ClassLoaderUtils.setThreadContextClassLoader(this.classLoader);
                try {
                    this.internelServlet.destroy();
                }finally {
                    ClassLoaderUtils.setThreadContextClassLoader(contextLoader);
                }
            }

            this.classLoader = null;
            this.internelServlet = null;
            this.initException = null;
        }
        this.invokeTimes = null;
        super.destroy();
    }

    /**
     * 添加服务
     *
     * @param reference  服务引用
     * @param repository 服务仓储
     * @return {@link Servlet},返回使用的服务实例,如果不使用服务，则返回null.
     */
    @Override
    public synchronized Servlet addService(ServiceReference<Servlet> reference, ServiceRepository repository) {
        if (this.internelServlet == null) {
            return repository.getService(reference).map(servlet ->{
                ClassLoader contextLoader =
                        ClassLoaderUtils.setThreadContextClassLoader(this.classLoader);
                try {
                    this.invokeTimes.incrementAndGet();
                    servlet.init(this.getServletConfig());
                    this.internelServlet = servlet;
                    this.initException = null;
                }catch(ServletException e) {
                    String text = servlet.getServletConfig().getInitParameter(IGNORE_INIT_ERROR);
                    if (text == null) {
                    	text = "false";
                    }
                    if (Boolean.parseBoolean(text)) {
                    	if (LOG.isWarnEnabled()) {
                            LOG.warn(e.getMessage(), e);
                        }
                    	this.internelServlet = servlet;
                    }else {
                    	this.initException = e;
                    	if (LOG.isErrorEnabled()) {
                            LOG.error(e.getMessage(), e);
                        }
                    }
                }finally {
                    this.invokeTimes.decrementAndGet();
                    ClassLoaderUtils.setThreadContextClassLoader(contextLoader);
                }
                return servlet;
            }).orElse(null);
        }
        return null;
    }

    /* (non-Javadoc)
     * @see com.massyframework.assembly.service.ServiceTrackerCustomizer#removeService(com.massyframework.assembly.service.ServiceReference, java.lang.Object)
     */
    @Override
    public synchronized void removeService(ServiceReference<Servlet> reference, Servlet service) {
        if (this.internelServlet != null) {
        	this.internelServlet.destroy();
            this.internelServlet = null;
            this.initException = null;

            //等待调用全部完成
            while (this.invokeTimes.get() != 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                }
            }
            ClassLoader contextLoader =
                    ClassLoaderUtils.setThreadContextClassLoader(this.classLoader);
            try {
                service.destroy();
            }finally {
                ClassLoaderUtils.setThreadContextClassLoader(contextLoader);
            }
        }
    }

    /**
     * 生成过滤字符串
     * @return {@link String}
     */
    protected String genericFilterString(){
        StringBuilder builder = new StringBuilder();
        builder.append("(&")
		        .append("(").append(ServiceReference.MODULE_GROUPID)
		        .append("=").append(this.getAssociatedModule().getGroupId()).append(")")
		        .append("(").append(ServiceReference.MODULE_MODULEID)
		        .append("=").append(this.getAssociatedModule().getModuleId()).append(")")
		        .append("(").append(ServiceReference.MODULE_VERSION)
		        .append("=").append(this.getAssociatedModule().getVersion()).append(")")
                .append("(").append(ServiceReference.SERVICE_NAME)
                .append("=").append(this.getServletConfig().getServletName()).append(")")
                .append(")");
        return builder.toString();
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        Module<?> module = this.getAssociatedModule();
        return module == null ?
                this.getClass().getSimpleName() :
                this.getClass().getSimpleName() + "[module=" + module.toIdentifier() + "].";
    }
}
